/**
 * Computer Graphics Lecture WS 2009/2010 Ulm University
 * Creator: Manuel Finckh
 * Email:   manuel.finckh@uni-ulm.de
 */

#ifndef RTSTRUCTS_H
#define RTSTRUCTS_H

#include "utils/vec.h"

#define RAY_MAX FLT_MAX
#define RAY_EPS 0.0001f

inline float minf(const float a, const float b) {
  return a < b ? a : b;
}
inline float maxf(const float a, const float b) {
  return a > b ? a : b;
}

struct Ray { // 32 Byte
  Vec3 origin;
  Vec3 dir;
  float tmin, tmax;

  inline Ray() { }

  inline Ray(const Vec3 &origin, const Vec3 &dir, const float tmin, const float tmax) : origin(origin), dir(dir), tmin(tmin), tmax(tmax) { }
};

struct HitRec {
  float dist;
  int id;
  int depth;

  inline HitRec() { dist = RAY_MAX; id = -1; depth = 0;};
  inline HitRec(float d, int i) { dist = d; id = i; depth = 0; };
  inline HitRec(float d, int i, int _depth) { dist = d; id = i; depth = _depth; };
  inline void setDepth(int d){depth = d;}
};

struct AABB {
  Vec3 bounds[2];

  inline AABB() { // empty box
    bounds[0] = Vec3(FLT_MAX);
    bounds[1] = Vec3(-FLT_MAX);
  }

  inline AABB(Vec3 bmin, Vec3 bmax) {
    bounds[0] = bmin;
    bounds[1] = bmax;
  }

  inline void extend(const AABB &bb) {
    bounds[0].minf(bb.bounds[0]);
    bounds[1].maxf(bb.bounds[1]);
  }

  inline bool intersect(const Ray &r, float &intervalMin, float &intervalMax, const Vec3 &invRayDir, const unsigned int raySign[3][2]) const {
    const float xMin = (bounds[raySign[0][0]][0] - r.origin[0]) * invRayDir[0];
    const float xMax = (bounds[raySign[0][1]][0] - r.origin[0]) * invRayDir[0];

    const float yMin = (bounds[raySign[1][0]][1] - r.origin[1]) * invRayDir[1];
    const float yMax = (bounds[raySign[1][1]][1] - r.origin[1]) * invRayDir[1];

    const float zMin = (bounds[raySign[2][0]][2] - r.origin[2]) * invRayDir[2];
    const float zMax = (bounds[raySign[2][1]][2] - r.origin[2]) * invRayDir[2];

    intervalMin = maxf(intervalMin, xMin);
    intervalMin = maxf(intervalMin, yMin);
    intervalMin = maxf(intervalMin, zMin);

    intervalMax = minf(intervalMax, xMax);
    intervalMax = minf(intervalMax, yMax);
    intervalMax = minf(intervalMax, zMax);

    return !(intervalMin > intervalMax);
  }

  inline int getMaxAxis() const {
    Vec3 ex = bounds[1] - bounds[0];
    return ex.maxIndex();
  }
};

struct Triangle {
  Vec3 v[3];

  inline void getExtents(const int axis, float &xmin, float &xmax) const {
    xmin = minf(v[0][axis], v[1][axis]);
    xmax = maxf(v[0][axis], v[1][axis]);
    xmin = minf(xmin, v[2][axis]);
    xmax = maxf(xmax, v[2][axis]);
  }

  inline AABB getAABB() const {
    AABB bbox;
    for (int axis = 0; axis < 3; axis++)
      getExtents(axis, bbox.bounds[0][axis], bbox.bounds[1][axis]);
    return bbox;
  }

  static AABB getAABB(const Triangle * const tris, const unsigned int nTris) {
    AABB bbox; // start with empty box

    for (unsigned int i = 0; i < nTris; i++) {
      for (int c = 0; c < 3; c++) {
        bbox.bounds[0][c] = minf(bbox.bounds[0][c], tris[i].v[0][c]);
        bbox.bounds[1][c] = maxf(bbox.bounds[1][c], tris[i].v[0][c]);
        bbox.bounds[0][c] = minf(bbox.bounds[0][c], tris[i].v[1][c]);
        bbox.bounds[1][c] = maxf(bbox.bounds[1][c], tris[i].v[1][c]);
        bbox.bounds[0][c] = minf(bbox.bounds[0][c], tris[i].v[2][c]);
        bbox.bounds[1][c] = maxf(bbox.bounds[1][c], tris[i].v[2][c]);
      }
    }
    return bbox;
  }

  // Moeller-Trumbore based Triangle Intersect
  inline bool intersect(const Ray &ray, HitRec &rec, const int tri_id) const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    const Vec3 pvec = Vec3::cross(ray.dir, edge2);

    const float det = edge1 * pvec;
    const float invDet = 1.0f / det;

    const Vec3 tvec = ray.origin - v[0];

    const float alpha = (tvec * pvec) * invDet;

    if (!(0.0f <= alpha) || alpha > 1.0f)
      return false;

    const Vec3 qvec = Vec3::cross(tvec, edge1);

    const float beta = (ray.dir * qvec) * invDet;

    if (!(0.0f <= beta) || alpha + beta > 1.0f)
      return false;

    const float t = (edge2 * qvec) * invDet;

    if (!(ray.tmin < t) || rec.dist < t)
      return false;

    rec.dist = t;
    rec.id = tri_id;

    return true;
  }

  inline bool intersectShadow(const Ray &ray) const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    const Vec3 pvec = Vec3::cross(ray.dir, edge2);

    const float det = edge1 * pvec;
    const float invDet = 1.0f / det;

    const Vec3 tvec = ray.origin - v[0];

    const float alpha = (tvec * pvec) * invDet;

    if (!(0.0f <= alpha) || alpha > 1.0f)
      return false;

    const Vec3 qvec = Vec3::cross(tvec, edge1);

    const float beta = (ray.dir * qvec) * invDet;

    if (!(0.0f <= beta) || alpha + beta > 1.0f)
      return false;

    const float t = (edge2 * qvec) * invDet;

    return (ray.tmin < t && ray.tmax >= t);
  }

  inline Vec3 getNormal() const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    Vec3 normal = Vec3::cross(edge1, edge2);
    normal.normalize();
    return normal;
  }

  inline void getBarycentric(const Ray &ray, float &alpha, float &beta) const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    const Vec3 pvec = Vec3::cross(ray.dir, edge2);

    const float det = edge1 * pvec;
    const float invDet = 1.0f / det;

    const Vec3 tvec = ray.origin - v[0];
    const Vec3 qvec = Vec3::cross(tvec, edge1);
    
    alpha = (tvec * pvec) * invDet;
    beta = (ray.dir * qvec) * invDet;
  }

  inline void sample(const float t1, const float t2, Vec3 &s, float &alpha, float &beta) const {
    const float tmp = sqrtf(t1);
    alpha = 1.0f - tmp;
    beta = t2 * tmp;

    Vec3 ab = v[1] - v[0];
    Vec3 ac = v[2] - v[0];

    s = v[0] + ab * alpha + ac * beta;
  }

  inline float area() const {
    Vec3 ab = v[1] - v[0];
    Vec3 ac = v[2] - v[0];

    Vec3 cross = Vec3::cross(ab, ac);
    return cross.length() * 0.5f;
  }
  
  inline Vec3 reflect(const Ray &ray) const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    Vec3 normal = Vec3::cross(edge1, edge2);
    normal.normalize();
    
    float N = normal * ray.dir;
    Vec3 dir = ray.dir - (2*N * normal);
    dir.normalize();
    return dir;
    
  }
  
  inline Vec3 reflect(const Ray &ray, const Vec3 &normal) const {
    const Vec3 edge1 = v[1] - v[0];
    const Vec3 edge2 = v[2] - v[0];

    //Vec3 normal = Vec3::cross(edge1, edge2);
    //normal.normalize();
    
    float N = normal * ray.dir;
    Vec3 dir = ray.dir - (2*N * normal);
    dir.normalize();
    return dir;
    
  }
};

#endif

